package feces;

import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import javax.persistence.PersistenceUnit;
import javax.persistence.Query;
import javax.persistence.RollbackException;
import javax.persistence.TemporalType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import feces.utility.IO;

/**
 * 数据库操作接口
 *
 * @author Bromine0x23
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class Database {
	
	@PersistenceUnit
	private static final EntityManagerFactory FACTORY = Persistence.createEntityManagerFactory("Feces"); 

	@PersistenceContext
	@Getter(AccessLevel.PROTECTED)
	private static final EntityManager manager = FACTORY.createEntityManager();

	
	/**
	 * 执行事务
	 *
	 * @param runnable 回调函数，在其中进行事务处理
	 * @return 执行是否成功
	 * @throws IllegalStateException
	 * @throws PersistenceException
	 */
	public static boolean doTransaction(
		Runnable runnable
	) throws PersistenceException {
		EntityTransaction transaction = getTransaction();
		try {
			transaction.begin();
			runnable.run();
			transaction.commit();
			return true;
		} catch (IllegalStateException | RollbackException exception){
			if (transaction.isActive()) {
				transaction.rollback();
			}
			return false;
		}
		
	}
	
	/**
	 * 获取事务对象
	 * 
	 * @return 事务对象
	 */
	public static EntityTransaction getTransaction() {
		return getManager().getTransaction();
	}
	
	public static CriteriaBuilder getCriteriaBuilder() {
		return getManager().getCriteriaBuilder();
	}
	
	/**
	 * 持久化实体数据
	 * 
	 * @param entity 实体
	 */
	public static <E> void add(E entity) {
		getManager().persist(entity);
	}
	
	/**
	 * 更新实体数据
	 * 
	 * @param entity 实体
	 * @return 更新后的实体
	 */
	public static <E> E update(E entity) {
		return getManager().merge(entity);
	}
	
	/**
	 * 移除持久化的实体
	 * 
	 * @param entity 实体
	 */
	public static <E> void remove(E entity) {
		getManager().remove(getManager().merge(entity));
	}
	
	/**
	 * 执行无绑定参数JPQL更新语句
	 * <pre>
	 * Example:
	 * 	executeUpdate("UPDATE e FROM E e set e.x = 1, e.y = 2");
	 * </pre>
	 * 
	 * @param sql JPQL语句
	 * @return 更新行数
	 */
	public static int executeUpdate(String sql) {
		Query query = getManager().createQuery(sql);
		return query.executeUpdate();
	}
	
	
	/**
	 * 执行有绑定参数的JPQL更新语句
	 * <pre>
	 * Example:
	 * 	executeUpdate("UPDATE e FROM E e set e.x = ?1, e.y = ?2", 10, 20);
	 * </pre>
	 * 
	 * @param sql JPQL语句
	 * @param params 绑定参数
	 * @return 更新行数
	 */
	public static <T> int executeUpdate(String sql, @SuppressWarnings("unchecked") T... params) {
		Query query = getManager().createQuery(sql);
		bindQueryParameters(query, params);
		return query.executeUpdate();
	}

	/**
	 * 执行使用命名绑定参数的JPQL更新语句
	 * <pre>
	 * Example:
	 * 	Map<String, Integer> params = new HashMap<String, Integer>();
	 * 	params.put("x", 10);
	 * 	params.put("y", 20);
	 * 	executeUpdate("UPDATE e FROM E e set e.x = :x, e.y = :y", params);
	 * </pre>
	 * 
	 * @param sql JPQL语句
	 * @param params 命名绑定参数
	 * @return 更新行数
	 */
	public static <T> int executeUpdate(String sql, Map<String, T> params) {
		Query query = getManager().createQuery(sql);
		bindQueryParameters(query, params);
		return query.executeUpdate();
	}

	public static <E> List<E> executeQuery(CriteriaQuery<E> query) {
		TypedQuery<E> typed_query = getManager().createQuery(query);
		return typed_query.getResultList();
	}
	
	/**
	 * 无绑定参数JPQL查询
	 * <pre>
	 * Example:
	 * 	executeUpdate("SELECT e FROM E e", E.class);
	 * </pre>
	 * 
	 * @param sql JPQL语句
	 * @param klass 查询结果类型
	 * @return 查询结果
	 */
	public static <E> List<E> executeQuery(String sql, Class<E> klass) {
		TypedQuery<E> query = getManager().createQuery(sql, klass);
		return query.getResultList();
	}
	
	/**
	 * 有绑定参数的JPQL查询
	 * <pre>
	 * Example:
	 * 	executeUpdate("SELECT DISTINCT e FROM E e WHERE e.x = ?1", E.class, 10);
	 * </pre>
	 * 
	 * @param sql JPQL语句
	 * @param klass 查询结果类型
	 * @param params 绑定参数
	 * @return 查询结果
	 */
	public static <E, T> List<E> executeQuery(String sql, Class<E> klass, @SuppressWarnings("unchecked") T... params) {
		TypedQuery<E> query = getManager().createQuery(sql, klass);
		bindQueryParameters(query, params);
		return query.getResultList();
	}
	
	/**
	 * 使用命名绑定参数的JPQL查询
	 * <pre>
	 * Example:
	 * 	Map<String, Integer> parameters = new HashMap<String, Integer>();
	 * 	parameters.put("x", 10);
	 * 	executeUpdate("SELECT DISTINCT e FROM E e WHERE e.x = :x", Entity.class, parameters);
	 * </pre>
	 * 
	 * @param sql JPQL语句
	 * @param klass 查询结果类型
	 * @param params 命名绑定参数
	 * @return 查询结果
	 */
	public static <E, T> List<E> executeQuery(String sql, Class<E> klass, Map<String, T> params) {
		TypedQuery<E> query = getManager().createQuery(sql, klass);
		bindQueryParameters(query, params);
		return query.getResultList();
	}
	
	/**
	 * 无绑定参数的JPQL命名查询
	 * 
	 * @param name 查询名
	 * @param klass 查询结果类型
	 * @return 查询结果
	 */
	public static <E> List<E> executeNamedQuery(String name, Class<E> klass) {
		TypedQuery<E> query = getManager().createNamedQuery(name, klass);
		return query.getResultList();
	}

	/**
	 * 有绑定参数的JPQL命名查询
	 * 
	 * @param name 查询名
	 * @param params 绑定参数
	 * @param klass 查询结果类型
	 * @return 查询结果
	 */
	public static <E, T> List<E> executeNamedQuery(String name, Class<E> klass, @SuppressWarnings("unchecked") T... params) {
		TypedQuery<E> query = getManager().createNamedQuery(name, klass);
		bindQueryParameters(query, params);
		return query.getResultList();
	}

	/**
	 * 使用命名绑定参数的JPQL命名查询
	 * 
	 * @param name 查询名
	 * @param klass 查询结果类型
	 * @param params 命名绑定参数
	 * @return 查询结果
	 */
	public static <E, T> List<E> executeNamedQuery(String name, Class<E> klass, Map<String, T> params) {
		TypedQuery<E> query = getManager().createNamedQuery(name, klass);
		bindQueryParameters(query, params);
		return query.getResultList();
	}

	/**
	 * 查找所有实体
	 * @param klass 实体类类型
	 * @return 所有实体
	 */
	public static <E> List<E> findAll(Class<E> klass) {
		return executeQuery(IO.sprintf("FROM %s AS e", klass.getSimpleName()), klass);
	}
	
	/**
	 * @param klass 实体类类型
	 * @param key 主键
	 * @return 主键对应的实体
	 */
	public static <E, K> E find(Class<E> klass, K key) {
		return key != null ? getManager().find(klass, key) : null;
	}

	/**
	 * 绑定查询参数
	 *
	 * @param query 查询对象
	 * @param params 参数
	 */
	@SafeVarargs
	private static <T> void bindQueryParameters(Query query, T... params) {
		for (int i = 1; i <= params.length; ++i) {
			Object param = params[i-1];
			if (param instanceof Timestamp) {
				query.setParameter(i, (Date) param, TemporalType.TIMESTAMP);
			} else if (param instanceof Time) {
				query.setParameter(i, (Date) param, TemporalType.TIME);
			} else if (param instanceof Date) {
				query.setParameter(i, (Date) param, TemporalType.DATE);
			} else {
				query.setParameter(i, param);
			}
		}
	}

	/**
	 * 绑定查询参数
	 *
	 * @param query 查询对象
	 * @param params 命名参数
	 */
	private static <T> void bindQueryParameters(Query query, Map<String, T> params) {
		for (Map.Entry<String, T> entry : params.entrySet()) {
			String key = entry.getKey();
			Object value = entry.getValue();
			if (value instanceof Timestamp) {
				query.setParameter(key, (Date) value, TemporalType.TIMESTAMP);
			} else if (value instanceof Time) {
				query.setParameter(key, (Date) value, TemporalType.TIME);
			} else if (value instanceof Date) {
				query.setParameter(key, (Date) value, TemporalType.DATE);
			} else {
				query.setParameter(key, value);
			}
		}
	}
}
